/**
 * fshows.com
 * Copyright (C) 2013-2020 All Rights Reserved.
 */
package com.fshows.leshuapay.sdk.client.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.fshows.leshuapay.sdk.client.LeshuaPaymentClient;
import com.fshows.leshuapay.sdk.enums.LeShuaSignTypeEnum;
import com.fshows.leshuapay.sdk.enums.LeshuaSettlementApiEnum;
import com.fshows.leshuapay.sdk.exception.LeshuaException;
import com.fshows.leshuapay.sdk.request.LeshuaBizRequest;
import com.fshows.leshuapay.sdk.request.settlement.MerchantSettlementOrderRequest;
import com.fshows.leshuapay.sdk.response.LeshuaBaseResponse;
import com.fshows.leshuapay.sdk.response.settlement.MerchantSettlementOrderBaseResponse;
import com.fshows.leshuapay.sdk.response.settlement.MerchantSettlementOrderResponse;
import com.fshows.leshuapay.sdk.util.LeshuaSignature;
import com.fshows.leshuapay.sdk.util.ReqSerialNoUtil;
import com.fshows.leshuapay.sdk.util.SignUtil;
import com.fshows.leshuapay.sdk.util.ValidateUtil;
import com.google.common.collect.Maps;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentSkipListMap;

/**
 * 商户打款clientImpl
 *
 * @author zhaoyi
 * @version LeshuaPaymentClientImpl.java, v 0.1 2020-07-30 16:57 zhaoyi
 */
@Slf4j
@Data
public class LeshuaPaymentClientImpl implements LeshuaPaymentClient {

    private String agentId;

    private String serverUrl;

    private String privateKey;

    /**
     * 从connectManager获取Connection 超时时间
     */
    private static final int CONNECTION_REQUEST_TIMEOUT = 10000;
    /**
     * 读取数据的超时时间
     */
    private static final int SOCKET_TIMEOUT = 10000;
    /**
     * 连接超时时间
     */
    private static final int CONNECT_TIMEOUT = 10000;
    private static CloseableHttpClient httpclient = HttpClients.createDefault();
    private Integer timeout;

    /**
     * 乐刷业务成功状态码
     */
    private static String SUCCESS_CODE = "0";

    /**
     * 请求响应成功状态码
     */
    private static int STATUS_SUCCESS = 200;

    public LeshuaPaymentClientImpl(String agentId, String serverUrl, String privateKey) {
        this.agentId = agentId;
        this.serverUrl = serverUrl;
        this.privateKey = privateKey;
    }

    public LeshuaPaymentClientImpl(String agentId, String serverUrl, String privateKey, Integer timeout) {
        this(agentId, serverUrl, privateKey);
        this.timeout = timeout;
    }

    @Override
    public MerchantSettlementOrderResponse querySettlementOrder(MerchantSettlementOrderRequest request, LeshuaSettlementApiEnum apiEnum) throws LeshuaException {
        ValidateUtil.validateWithThrow(request);
        MerchantSettlementOrderResponse response = new MerchantSettlementOrderResponse();
        Map<String, Object> requestMap = new ConcurrentSkipListMap<>();
        requestMap.put("agentId", agentId);
        requestMap.put("billId", request.getBillId());
        requestMap.put("type", request.getType());
        String sign = LeshuaSignature.generateMd5Sign(requestMap, null, privateKey);
        if (sign != null) {
            sign = sign.toLowerCase();
        }
        requestMap.put("sign", sign);
        try {
            String url = serverUrl + apiEnum.getValue();

            HttpResponse httpResponse = HttpUtil.createPost(url).timeout(CONNECTION_REQUEST_TIMEOUT).form(requestMap).execute();
            if (httpResponse.getStatus() != STATUS_SUCCESS) {
                String errorMsg = StrUtil.format("请求乐刷接口失败，errorCode={}", httpResponse.getStatus());
                throw new LeshuaException(errorMsg);
            }
            String responseStr = httpResponse.body();

            log.info("querySettlementOrder >> 乐刷结算单查询 >> url={},request={},response={}", url, JSON.toJSONString(requestMap), responseStr);
            JSONObject jsonObject = JSON.parseObject(responseStr);
            String errorCode = jsonObject.getString("error_code");
            if (SUCCESS_CODE.equalsIgnoreCase(errorCode)) {
                JSONObject dataObject = jsonObject.getJSONObject("data");
                response = dataObject.toJavaObject(MerchantSettlementOrderResponse.class);
            }
            response.setErrorCode(jsonObject.getString("error_code"));
            response.setErrorMsg(jsonObject.getString("error_msg"));
            return response;
        } catch (Exception e) {
            log.error("乐刷接口请求异常,request={},e={}", request, ExceptionUtils.getStackTrace(e));
            throw new LeshuaException(e.getMessage(), e);
        }
    }

    @Override
    public <Req, Res> MerchantSettlementOrderBaseResponse<Res> execute(Req request, LeshuaSettlementApiEnum apiEnum, Class<Res> resClass) throws LeshuaException {
        ValidateUtil.validateWithThrow(request);
        MerchantSettlementOrderBaseResponse<Res> response = new MerchantSettlementOrderBaseResponse<>();
        String url = serverUrl + apiEnum.getValue();
        String responseStr = executeHttp(JSON.toJSONString(request), url);
        JSONObject jsonObject = JSON.parseObject(responseStr);
        String errorCode = jsonObject.getString("error_code");
        String errorMsg = jsonObject.getString("error_msg");

        response.setErrorCode(errorCode);
        response.setErrorMsg(errorMsg);

        if (SUCCESS_CODE.equalsIgnoreCase(errorCode)) {
            response.setPageNo(jsonObject.getInteger("pageNo"));
            response.setPageSize(jsonObject.getInteger("pageSize"));
            response.setTotalCount(jsonObject.getInteger("totalCount"));
            JSONArray jsonArray = jsonObject.getJSONArray("list");
            List<Res> orders = jsonArray.toJavaList(resClass);
            response.setList(orders);
        }
        return response;
    }

    /**
     * 提现等操作
     *
     * @param request 请求参数
     * @param apiEnum 网关
     * @return LeshuaBaseResponse
     */
    @Override
    public <R> LeshuaBaseResponse<R> execute(LeshuaBizRequest<R> request, LeshuaSettlementApiEnum apiEnum, LeShuaSignTypeEnum signTypeEnum) throws LeshuaException {
        ValidateUtil.validateWithThrow(request);

        String reqSerialNo = ReqSerialNoUtil.getReqSerialNo();
        String url = serverUrl + apiEnum.getValue();
        String sign = getMd5SignWithReqSerialNoAndVersion(JSON.toJSONString(request), signTypeEnum, reqSerialNo);

        Map<String, Object> requestMap = Maps.newHashMap();
        requestMap.put("agentId", agentId);
        requestMap.put("version", SignUtil.DEFAULT_VERSION);
        requestMap.put("reqSerialNo", reqSerialNo);
        requestMap.put("sign", sign);
        requestMap.put("data", request);

        String requestJsonStr = JSON.toJSONString(requestMap);

        LeshuaBaseResponse<R> response = new LeshuaBaseResponse<>();
        try {
            HttpResponse httpResponse = HttpUtil.createPost(url).timeout(CONNECTION_REQUEST_TIMEOUT).body(requestJsonStr).execute();
            if (httpResponse.getStatus() != STATUS_SUCCESS) {
                log.warn("乐刷接口请求异常 >> url={},request={}", url, requestJsonStr);
                String errorMsg = StrUtil.format("乐刷接口请求异常,errorCode={}", httpResponse.getStatus());
                throw new LeshuaException(errorMsg);
            }
            String responseStr = httpResponse.body();
            log.info("乐刷请求处理 >> url={},request={},response={}", url, requestJsonStr, responseStr);
            JSONObject jsonObject = JSON.parseObject(responseStr);
            response.setRespCode(StrUtil.nullToDefault(jsonObject.getString("code"), jsonObject.getString("error_code")));
            response.setRespMsg(StrUtil.nullToDefault(jsonObject.getString("msg"), jsonObject.getString("error_msg")));
            response.setReqSerialNo(jsonObject.getString("reqSerialNo"));
            response.setData(JSON.parseObject(jsonObject.getString("data"), request.getResponseClass()));
        } catch (Exception e) {
            log.error("乐刷接口请求异常 >> url={},request={},e={}", url, requestJsonStr, ExceptionUtils.getStackTrace(e));
            throw new LeshuaException(e.getMessage());
        }

        return response;
    }

    /**
     * 获取乐刷MD5签名
     *
     * @param content      请求字符串
     * @param signTypeEnum 签名规则
     * @param reqSerialNo  请求流水号
     * @return String
     */
    private String getMd5SignWithReqSerialNoAndVersion(String content, LeShuaSignTypeEnum signTypeEnum, String reqSerialNo) throws LeshuaException {
        String sign;
        //获取签名
        sign = SignUtil.getSignWithReqSerialNoAndVersion(content, privateKey, reqSerialNo, SignUtil.DEFAULT_VERSION);
        //签名需要转小写
        if (LeShuaSignTypeEnum.TYPE_WITH_TOLOWERCASE_MD5.equals(signTypeEnum)) {
            sign = sign.toLowerCase();
        }
        return sign;
    }

    private String executeHttp(String request, String url) throws LeshuaException {
        //获取签名值
        String sign = SignUtil.getSettlementOrderSign(request, privateKey);
        RequestConfig requestConfig;
        if (timeout == null || timeout <= 0) {
            requestConfig = RequestConfig.custom().setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT)
                    .setSocketTimeout(SOCKET_TIMEOUT).setConnectTimeout(CONNECT_TIMEOUT).build();
        } else {
            requestConfig = RequestConfig.custom().setConnectionRequestTimeout(timeout)
                    .setSocketTimeout(timeout).setConnectTimeout(timeout).build();
        }
        HttpPost httppost = new HttpPost(url);
        httppost.setConfig(requestConfig);
        MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
        multipartEntityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
        multipartEntityBuilder.setCharset(Charset.forName(SignUtil.DEFAULT_CHARSET));
        String reqSerialNo = ReqSerialNoUtil.getReqSerialNo();
        //代理商编号
        multipartEntityBuilder.addPart("agentId", new StringBody(agentId, ContentType.APPLICATION_JSON));
        //版本
        multipartEntityBuilder.addPart("version", new StringBody(SignUtil.DEFAULT_VERSION, ContentType.APPLICATION_JSON));
        //请求流水号
        multipartEntityBuilder.addPart("reqSerialNo", new StringBody(reqSerialNo, ContentType.APPLICATION_JSON));
        //数据内容
        multipartEntityBuilder.addPart("data", new StringBody(request, ContentType.APPLICATION_JSON));
        //签名值
        multipartEntityBuilder.addPart("sign", new StringBody(sign, ContentType.APPLICATION_JSON));
        HttpEntity reqEntity = multipartEntityBuilder.build();
        httppost.setEntity(reqEntity);
        CloseableHttpResponse httpResponse = null;
        HttpEntity resEntity = null;
        try {
            httpResponse = httpclient.execute(httppost);
            if (httpResponse.getStatusLine().getStatusCode() != STATUS_SUCCESS) {
                String errorMsg = StrUtil.format("请求乐刷接口失败，errorCode={},errorMsg={}", httpResponse.getStatusLine().getStatusCode(), httpResponse.getStatusLine().getReasonPhrase());
                throw new LeshuaException(errorMsg);
            }
            resEntity = httpResponse.getEntity();
            String responseStr = EntityUtils.toString(resEntity, SignUtil.DEFAULT_CHARSET);
            log.info("请求乐刷接口 >> url={},request={},response={}", url, request, responseStr);
            EntityUtils.consume(resEntity);
            httpResponse.close();
            return responseStr;
        } catch (Exception e) {
            log.error("乐刷接口请求异常 >> url={},reqId={},request={},e={}", url, reqSerialNo, request, ExceptionUtils.getStackTrace(e));
            throw new LeshuaException(e.getMessage(), e);
        } finally {
            try {
                EntityUtils.consume(resEntity);
            } catch (IOException e) {
                log.error("释放HttpEntity出错，错误信息 >> url={},reqId={},request={},e={}", url, reqSerialNo, request, e);
            }
            if (httpResponse != null) {
                try {
                    httpResponse.close();
                } catch (IOException e) {
                    log.error("关闭HttpResponse出错，错误信息 >> url={},reqId={},request={},e={}", url, reqSerialNo, request, e);
                }
            }
        }
    }
}
